/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2002 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package asruby.lang {

import asruby.Ruby;
import asruby.exceptions.JumpException;
import asruby.runtime.Block;
import asruby.runtime.CallbackFactory;
import asruby.runtime.IRubyObject;
import asruby.runtime.ObjectAllocator;
import asruby.runtime.ThreadContext;
import asruby.runtime.methods.DynamicMethod;

/** 
 * The RubyMethod class represents a RubyMethod object.
 * 
 * You can get such a method by calling the "method" method of an object.
 * 
 * Note: This was renamed from Method.java
 * 
 * @author  jpetersen
 * @since 0.2.3
 */
public class RubyMethod extends RubyObject {
    protected  var implementationModule:RubyModule;
    protected  var methodName:String;
    protected  var originModule:RubyModule;
    protected  var originName:String;
    protected  var method:DynamicMethod;
    protected  var receiver:IRubyObject;

    function RubyMethod(runtime:Ruby, rubyClass:RubyClass){
        super(runtime, rubyClass);
    }

    /** Create the RubyMethod class and add it to the Ruby runtime.
     * 
     */
    public static function createMethodClass(runtime:Ruby):RubyClass{
        // TODO: NOT_ALLOCATABLE_ALLOCATOR is probably ok here. Confirm. JRUBY-415
        var methodClass: RubyClass =  runtime.defineClass("Method", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setMethod(methodClass);
        
        methodClass.defineAnnotatedMethods(RubyMethod);
        
        return methodClass;
    }

    public static function newMethod(
         implementationModule:RubyModule,
         methodName:String,
         originModule:RubyModule,
         originName:String,
         method:DynamicMethod,
         receiver:IRubyObject):RubyMethod {
        var runtime: Ruby =  implementationModule.getRuntime();
        var newMethod: RubyMethod =  new RubyMethod(runtime, runtime.getMethod());

        newMethod.implementationModule = implementationModule;
        newMethod.methodName = methodName;
        newMethod.originModule = originModule;
        newMethod.originName = originName;
        newMethod.method = method.getRealMethod();
        newMethod.receiver = receiver;

        return newMethod;
    }

    /** Call the method.
     * 
     */
    public  function call(args:Array, block:Block):IRubyObject{
    	//assert args != null;
        var tc: ThreadContext =  getRuntime().getCurrentContext();

    	method.getArity().checkArity(getRuntime(), args);
        
        // FIXME: should lastClass be implementation module for a Method?
        return method.call(tc, receiver, implementationModule, methodName, args, block);
    }

    /** Returns the number of arguments a method accepted.
     * 
     * @return the number of arguments of a method.
     */
    public  function arity():RubyFixnum{
        return getRuntime().newFixnum(method.getArity().getValue());
    }

    public override function op_equal(other:IRubyObject):RubyBoolean{
        if (!(other instanceof RubyMethod)) return getRuntime().getFalse();
        var otherMethod: RubyMethod =  RubyMethod(other);
        return getRuntime().newBoolean(implementationModule == otherMethod.implementationModule &&
                                       originModule == otherMethod.originModule &&
                                       receiver == otherMethod.receiver &&
                                       method.getRealMethod() == otherMethod.method.getRealMethod());
    }

    public  function rbClone():RubyMethod{
        return newMethod(implementationModule, methodName, originModule, originName, method, receiver);
    }

    /** Create a Proc object.
     * 
     */
    public  function to_proc(unusedBlock:Block):IRubyObject{
        var f: CallbackFactory =  getRuntime().callbackFactory(RubyMethod);
        var r: Ruby =  getRuntime();
        var tc: ThreadContext =  r.getCurrentContext();
        var block: Block =  MethodBlock.createMethodBlock(tc, tc.getCurrentScope(), f.getBlockMethod("bmcall"), this, r.getTopSelf());
        
        while (true) {
            try {
                // FIXME: We should not be regenerating this over and over
                return f.getSingletonMethod("mproc").execute(getRuntime().getNil(), 
                        IRubyObject.NULL_ARRAY, block);
            } catch (bj:JumpException.BreakJump) {
                    return IRubyObject(bj.getValue());
            } catch (rj:JumpException.ReturnJump) {
                    return IRubyObject(rj.getValue());
            } catch (rj:JumpException.RetryJump) {
                    // Execute iterateMethod again.
            }
        }
    }

    /** Create a Proc object which is called like a ruby method.
     *
     * Used by the RubyMethod#to_proc method.
     *
     */
    public static function mproc(recv:IRubyObject, block:Block):IRubyObject{
    	var runtime: Ruby =  recv.getRuntime();
    	var tc: ThreadContext =  runtime.getCurrentContext();
        
        tc.preMproc();
        
        try {
            return RubyKernel.proc(recv, block);
        } finally {
            tc.postMproc();
        }
    }

    /** Delegate a block call to a bound method call.
     *
     * Used by the RubyMethod#to_proc method.
     *
     */
    public static function bmcall(blockArg:IRubyObject, arg1:IRubyObject, self:IRubyObject, unusedBlock:Block):IRubyObject{
 /*        if (blockArg instanceof RubyArray) {
            // ENEBO: Very wrong
            return (RubyMethod(arg1).call(((RubyArray) blockArg).toJavaArray(), Block.NULL_BLOCK);
        }
        // ENEBO: Very wrong
        return (RubyMethod(arg1).call(new IRubyObject[] { blockArg }, Block.NULL_BLOCK); */
    }
/*
    public  function unbind(unusedBlock:Block):RubyUnboundMethod{
        var unboundMethod: RubyUnboundMethod = 
        	RubyUnboundMethod.newUnboundMethod(implementationModule, methodName, originModule, originName, method);
        unboundMethod.infectBy(this);
        
        return unboundMethod; 
    }
    
    public  function inspect():IRubyObject{
        var buf: StringBuilder =  new StringBuilder("#<");
        var delimeter: char =  '#';
        
        buf.append(getMetaClass().getRealClass().getName()).append(": ");
        
        if (implementationModule.isSingleton()) {
            var attached: IRubyObject =  ((MetaClass) originModule).getAttached();
            if (receiver == null) {
                buf.append(implementationModule.inspect().toString());
            } else if (receiver == attached) {
                buf.append(attached.inspect().toString());
                delimeter = '.';
            } else {
                buf.append(receiver.inspect().toString());
                buf.append('(').append(attached.inspect().toString()).append(')');
                delimeter = '.';
            }
        } else {
            buf.append(originModule.getName());
            
            if (implementationModule != originModule) {
                buf.append('(').append(implementationModule.getName()).append(')');
            }
        }
        
        buf.append(delimeter).append(methodName).append('>');
        
        var str: RubyString =  getRuntime().newString(buf.toString());
        str.setTaint(isTaint());
        return str;
    } */
}

}